
#include <BQuater>
#include <BRender>
#include <BShape>
#include <BCamera>
#include "member_BCamera.h"

using namespace BWE;

#define member					(*(member_BCamera*)_ptr)
#define member_allocate()		_ptr = new member_BCamera(this)
#define member_release()		delete (member_BCamera*)_ptr

BCamera::BCamera(const BString& name) : BNode(name)
{
	member_allocate();
	member.center.set(0.0f, 1.0f, 0.0f);
}
BCamera::~BCamera()
{
	member_release();
}

void BCamera::setCenter(BReal x, BReal y, BReal z)
{
	setCenter(BVector(x, y, z));
}
void BCamera::setCenter(const BVector& center)
{
	if (member.center != center)
	{
		member.center = center;
		emit(Signal_Changed);
		this->fresh();
	}
}
const BVector& BCamera::center() const
{
	return member.center;
}
const BVector& BCamera::direct() const
{
	return member.direct;
}

void BCamera::setUp(BReal x, BReal y, BReal z)
{
	setUp(BVector(x, y, z));
}
void BCamera::setUp(const BVector& up)
{
	if (member.up != up)
	{
		member.up = up;
		emit(Signal_Changed);
		this->fresh();
	}
}
const BVector& BCamera::up() const
{
	return member.up;
}

void BCamera::setOrtho(bool ortho)
{
	if (member.ortho != ortho)
	{
		member.ortho = ortho;
		member.adjust();
		emit(Signal_Changed);
		this->fresh();
	}
}
bool BCamera::ortho() const
{
	return member.ortho;
}

void BCamera::setFovy(BReal fovy)
{
	if (member.fovy != fovy)
	{
		member.fovy = fovy;
		member.adjust();
		emit(Signal_Changed);
		this->fresh();
	}
}
BReal BCamera::fovy() const
{
	return member.fovy;
}

void BCamera::setAspect(BReal aspect)
{
	if (member.aspect != aspect)
	{
		member.aspect = aspect;
		if (member.ortho)
			member.lensWidth = member.lensHeight * aspect;
		else
			member.adjust();
		emit(Signal_Changed);
		this->fresh();
	}
}
BReal BCamera::aspect() const
{
	return member.aspect;
}

void BCamera::setNear(BReal near)
{
	if (member.near != near)
	{
		member.near = near;
		member.adjust();
		emit(Signal_Changed);
		this->fresh();
	}
}
BReal BCamera::near() const
{
	return member.near;
}

void BCamera::setFar(BReal far)
{
	if (member.far != far)
	{
		member.far = far;
		emit(Signal_Changed);
		this->fresh();
	}
}
BReal BCamera::far() const
{
	return member.far;
}

void BCamera::setLensWidth(BReal lensWidth)
{
	if (member.lensWidth != lensWidth)
	{
		member.lensWidth = lensWidth;
		member.adjust();
		emit(Signal_Changed);
		this->fresh();
	}
}
BReal BCamera::lensWidth() const
{
	return member.lensWidth;
}

void BCamera::setLensHeight(BReal lensHeight)
{
	if (member.lensHeight != lensHeight)
	{
		member.lensHeight = lensHeight;
		member.adjust();
		emit(Signal_Changed);
		this->fresh();
	}
}
BReal BCamera::lensHeight() const
{
	return member.lensHeight;
}

void BCamera::setPixelWidth(int pixelWidth)
{
	setPixelSize(BSize(pixelWidth, member.pixelSize.height()));
}
int BCamera::pixelWidth() const
{
	return member.pixelSize.width();
}

void BCamera::setPixelHeight(int pixelHeight)
{
	setPixelSize(BSize(member.pixelSize.width(), pixelHeight));
}
int BCamera::pixelHeight() const
{
	return member.pixelSize.height();
}

void BCamera::setPixelSize(int pixelWidth, int pixelHeight)
{
	setPixelSize(BSize(pixelWidth, pixelHeight));
}
void BCamera::setPixelSize(const BSize& pixelSize)
{
	if (member.pixelSize != pixelSize)
	{
		member.pixelSize = pixelSize;
		member.fbo_resized = true;
		emit(Signal_Changed);
		this->fresh();
	}
}
const BSize& BCamera::pixelSize() const
{
	return member.pixelSize;
}

void BCamera::setPixelSizePolicy(Policy sizePolicy)
{
	if (member.pixelSizePolicy != sizePolicy)
	{
		member.pixelSizePolicy = sizePolicy;
		emit(Signal_Changed);
	}
}
Policy BCamera::pixelSizePolicy() const
{
	return member.pixelSizePolicy;
}

BReal BCamera::unit(BReal dist) const
{
	int pixelWidth = member.pixelSize.width();
	double phw = pixelWidth * 0.5;
	double lhw = member.lensWidth * 0.5;
	double offsetX = lhw / phw;
	return offsetX * dist / member.near;
}
BReal BCamera::unit(const BVector& point) const
{
	double dist = (this->position() - point).length();
	return unit(dist);
}
BVector BCamera::lensVertex(const BVector& vertex) const
{
	const IFrustum& frustum = member.frustum;
	if (member.lensWidth < -1e-6 || member.lensHeight < -1e-6)
		return (frustum.near_v0 + frustum.near_v2) * BReal(0.5);
	BVector intvec;
	BLine line(this->position(), vertex);
	if (line.intersect(frustum.near_v0, frustum.near_v1, frustum.near_v2, frustum.near_v3, intvec, true))
	{
		return intvec;
	}
	return (frustum.near_v0 + frustum.near_v2) * BReal(0.5);
}
BPoint BCamera::pixelPoint(const BVector& vertex) const
{
	if(member.lensWidth < -1e-6 || member.lensHeight < -1e-6)
		return BPoint(member.pixelSize.width() / 2, member.pixelSize.height() / 2);
	BVector intvec;
	BLine line(this->position(), vertex);
	const IFrustum& frustum = member.frustum;
	if (line.intersect(frustum.plane_near, intvec))
	{
		BVector direct = member.center - this->position();
		direct.normalize();
		BVector right = direct.cross(member.up);
		right.normalize();
		BVector up = right.cross(direct);
		up.normalize();
		BReal rx = (intvec - frustum.near_v0).dot(right) / member.lensWidth;
		BReal ry = (intvec - frustum.near_v0).dot(-up) / member.lensHeight;
		return BPoint(int(rx * member.pixelSize.width()), int(ry * member.pixelSize.height()));
	}
	return BPoint(member.pixelSize.width() / 2, member.pixelSize.height() / 2);
}

void BCamera::setColor(BByte r, BByte g, BByte b, BByte a)
{
	setColor(BColor(r, g, b, a));
}
void BCamera::setColor(const BColor& color)
{
	if (member.color != color)
	{
		member.color = color;
		emit(Signal_Changed);
		this->fresh();
	}
}
const BColor& BCamera::color() const
{
	return member.color;
}

bool BCamera::capture(BImage* image)
{
	if (!image || member.pixelSize.empty())
		return false;
	Format format = image->format();
	if (format == Format_None)
	{
		format = Format_RGBA;
		image->setFormat(format);
	}
	image->setSize(member.pixelSize);
	BByteArray* pixels = image->pixels();
	if (format == Format_RGBA && pixels)
	{
		glReadPixels(0, 0, member.pixelSize.width(), member.pixelSize.height(), GL_RGBA, GL_UNSIGNED_BYTE, pixels->data());
	}
	if (format == Format_RGB && pixels)
	{
		glReadPixels(0, 0, member.pixelSize.width(), member.pixelSize.height(), GL_RGB, GL_UNSIGNED_BYTE, pixels->data());
	}
	image->dirty();
	return true;
}
bool BCamera::capture(BImage* image, const BRect& rect)
{
	if (!image || rect.empty() || member.pixelSize.empty())
		return false;
	Format format = image->format();
	if (format == Format_None)
	{
		format = Format_RGBA;
		image->setFormat(format);
	}
	image->setSize(rect.size());
	int y = member.pixelSize.height() - rect.y() - rect.height();
	BByteArray* pixels = image->pixels();
	if (format == Format_RGBA && pixels)
	{
		glReadPixels(rect.x(), y, rect.width(), rect.height(), GL_RGBA, GL_UNSIGNED_BYTE, pixels->data());
	}
	if (format == Format_RGB && pixels)
	{
		glReadPixels(rect.x(), y, rect.width(), rect.height(), GL_RGB, GL_UNSIGNED_BYTE, pixels->data());
	}
	image->dirty();
	return true;
}

void BCamera::fresh() const
{
	member.refresh = true;
	emit(Signal_Fresh);
}

bool BCamera::query(const char* name, const BValue& value) const
{
	member.freshFrustum();
	if (0 == strcmp(name, "frustum"))
	{
		BVectorArray& points = value;
		points.reset();
		points.append(member.frustum.near_v0);
		points.append(member.frustum.near_v1);
		points.append(member.frustum.near_v2);
		points.append(member.frustum.near_v3);
		points.append(member.frustum.far_v0);
		points.append(member.frustum.far_v1);
		points.append(member.frustum.far_v2);
		points.append(member.frustum.far_v3);
		return true;
	}
	if (0 == strcmp(name, "lights"))
	{
		BLightArray& lights = value;
		lights = member.lights;
		return true;
	}
	if (0 == strcmp(name, "shapes"))
	{
		BMap<BShape*, BBox>* shapeMap = value;
		shapeMap->reset();
		for (int i = 0; i < member.shapeArray.size(); i++)
		{
			ShapeInfo& sinfo = member.shapeArray[i];
			BShape* shape = sinfo.shape;
			shapeMap->insert(shape, sinfo.box);
		}
		return true;
	}
	if (0 == strcmp(name, "shapes-transparent"))
	{
		BMap<BShape*, BBox>* transShapeQueue = value;
		transShapeQueue->reset();
		for (auto it = member.transShapeQueue.begin(); it.valid(); it++)
		{
			ShapeInfo& sinfo = *it;
			BShape* shape = sinfo.shape;
			transShapeQueue->insert(shape, sinfo.box);
		}
		return true;
	}
	if (0 == strcmp(name, "scene-items"))
	{
		ConstSceneOctTreeItemArray* sceneItems = value;
		sceneItems->reset();
		sceneItems->append(member.sceneItems);
		return true;
	}
	return false;
}

